//------------------------------------------------------------------------------
//  n2modelreader.cc
//  (C) 2007 Radon Labs GmbH
//------------------------------------------------------------------------------
#include "stdneb.h"
#include "models/load/n2modelreader.h"
#include "math/point.h"
#include "math/vector.h"
#include "models/attributes.h"
#include "models/nodes/transformnode.h"
#include "models/nodes/shapenode.h"
#include "system/byteorder.h"

namespace Models
{
ImplementClass(Models::N2ModelReader, 'N2MR', Models::ModelReader);

using namespace IO;
using namespace Util;
using namespace Math;
using namespace Attr;
using namespace System;

//------------------------------------------------------------------------------
/**
*/
N2ModelReader::N2ModelReader() :
    isSetToModel(false),
    isSetToModelNode(false)
{
    // empty
}

//------------------------------------------------------------------------------
/**
*/
N2ModelReader::~N2ModelReader()
{
    if (this->IsOpen())
    {
        this->Close();
    }
}

//------------------------------------------------------------------------------
/**
*/
bool
N2ModelReader::Open()
{
    n_assert(!this->IsOpen());
    n_assert(this->stream.isvalid());
    n_assert(this->stream->CanRead());
    n_assert(this->stream->CanSeek());
    n_assert(!this->stream->IsMapped());
    n_assert(this->modelResId.Value().IsValid());

    this->isSetToModel = false;
    this->isSetToModelNode = false;

    // open a proxy binary reader and position on root node
    this->binaryReader = BinaryReader::Create();
    this->binaryReader->SetStream(this->stream);
    this->binaryReader->SetStreamByteOrder(ByteOrder::LittleEndian);
    if (this->binaryReader->Open())
    {
        // make sure it's a binary N2 file
        FourCC magic(this->binaryReader->ReadUInt());
        if (magic == FourCC('NOB0'))
        {
            // skip the header data
            String headerData = this->binaryReader->ReadString();
            this->isOpen = true;
            return true;
        }
    }
    return false;
}

//------------------------------------------------------------------------------
/**
*/
void
N2ModelReader::Close()
{
    n_assert(this->IsOpen());
    this->binaryReader->Close();
    this->binaryReader = 0;
    this->isOpen = false;
}

//------------------------------------------------------------------------------
/**
*/
bool
N2ModelReader::SetToFirstModel()
{
    n_assert(this->isOpen);
    n_assert(!this->isSetToModel);
    n_assert(!this->isSetToModelNode);

    // note: there is only one Model per n2 file, so this method is kind of simple
    this->isSetToModel = true;
    this->isSetToModelNode = false;
    this->ReadModelData();
    return true;
}

//------------------------------------------------------------------------------
/**
*/
bool
N2ModelReader::SetToNextModel()
{
    n_assert(this->isOpen);
    n_assert(this->isSetToModel);
    n_assert(!this->isSetToModelNode);

    // note: there is only one Model per n2 file, so this method is kind of simple
    this->isSetToModel = false;
    this->isSetToModelNode = false;
    return false;
}

//------------------------------------------------------------------------------
/**
*/
bool
N2ModelReader::SetToFirstModelNode()
{
    n_assert(this->isOpen);
    n_assert(this->isSetToModel);
    n_assert(!this->isSetToModelNode);

    // the fourcc tag we read SHOULD be a _new, which marks
    // the beginning of a new object
    FourCC fourCC = this->binaryReader->ReadUInt();
    if (fourCC == FourCC('_new'))
    {
        this->isSetToModelNode = true;
        this->ReadModelNodeData();
        return true;
    }
    else
    {
        return false;
    }
}

//------------------------------------------------------------------------------
/**
*/
bool
N2ModelReader::SetToNextModelNode()
{
    n_assert(this->isOpen);
    n_assert(this->isSetToModel);
    n_assert(this->isSetToModelNode);

    // read the next FourCC tag
    const FourCC newFourCC('_new');
    const FourCC selFourCC('_sel');
    FourCC curFourCC = this->binaryReader->ReadUInt();
    if (curFourCC == newFourCC)
    {
        // a new model node
        this->isSetToModelNode = true;
        this->ReadModelNodeData();
    }
    else if (curFourCC == selFourCC)
    {
        // the end of a model node
        this->modelNodeStack.Pop();

        // skip relative path (we assume it's "..")
        String selPath = this->binaryReader->ReadString();

        // need to skip any additional sels
        while (((curFourCC = this->binaryReader->ReadUInt()) == selFourCC) && !this->Eof())
        {
            this->modelNodeStack.Pop();
            selPath = this->binaryReader->ReadString();
        }
        if (this->Eof())
        {
            return false;
        }
        // go back to last FourCC
        this->stream->Seek(-4, Stream::Current);
    }
    return true;
}

//------------------------------------------------------------------------------
/**
*/
void
N2ModelReader::ReadModelData()
{
    // just skip all data tags until we find a _new statement
    const FourCC newFourCC('_new');
    const FourCC selFourCC('_sel');
    FourCC curFourCC;
    bool stop = false; 
    do
    {
        // read next FourCC and check for special case
        curFourCC = this->binaryReader->ReadUInt();
        if ((curFourCC != newFourCC) && (curFourCC != selFourCC))
        {
            // skip data block
            ushort length = this->binaryReader->ReadUShort();
            this->stream->Seek(length, Stream::Current);
        }
        else
        {
            stop = true;
        }
    }
    while (!stop && !this->Eof());
    n_assert(curFourCC == newFourCC);

    // need to seek back to the beginning of the _new statement
    this->stream->Seek(-4, Stream::Current);
}

//------------------------------------------------------------------------------
/**
*/
void
N2ModelReader::ReadModelNodeData()
{
    // read the stuff following a '_new' tag
    String objClass = this->binaryReader->ReadString();
    String objName  = this->binaryReader->ReadString();
    if (objClass == "ntransformnode")
    {
        this->modelNodeRtti = &TransformNode::RTTI;
    }
    else if (objClass == "nshapenode")
    {
        this->modelNodeRtti = &ShapeNode::RTTI;
    }
    else
    {
        // unknown class, just simulate this with a transform node
        this->modelNodeRtti = &TransformNode::RTTI;
    }
    this->modelNodeName = objName;
    if (this->modelNodeStack.IsEmpty())
    {
        this->modelNodeParentName.Clear();
    }
    else
    {
        this->modelNodeParentName = this->modelNodeStack.Peek();
    }
    this->modelNodeStack.Push(objName);

    // read known attributes, skip unknown attributes
    // until a _new oder _sel tag occurs
    this->modelNodeAttrs.Clear();
    const FourCC newFourCC('_new');
    const FourCC selFourCC('_sel');
    FourCC curFourCC;
    bool stop = false; 
    do
    {
        // read next FourCC and check for special case
        curFourCC = this->binaryReader->ReadUInt();
        if ((curFourCC != newFourCC) && (curFourCC != selFourCC))
        {
            ushort length = this->binaryReader->ReadUShort();
            if (FourCC('SLCB') == curFourCC)
            {
                // .setlocalbox
                float x, y, z;
                x = this->binaryReader->ReadFloat();
                y = this->binaryReader->ReadFloat();
                z = this->binaryReader->ReadFloat();
                this->modelNodeAttrs.SetFloat4(Attr::BoxCenter, point(x, y, z));
                x = this->binaryReader->ReadFloat();
                y = this->binaryReader->ReadFloat();
                z = this->binaryReader->ReadFloat();
                this->modelNodeAttrs.SetFloat4(Attr::BoxExtents, vector(x, y, z));
            }
            else if (FourCC('SPOS') == curFourCC)
            {
                // .setposition
                float x = this->binaryReader->ReadFloat();
                float y = this->binaryReader->ReadFloat();
                float z = this->binaryReader->ReadFloat();
                this->modelNodeAttrs.SetFloat4(Attr::Position, point(x, y, z));
            }
            else if (FourCC('SQUT') == curFourCC)
            {
                // .setquat
                float4 q = this->binaryReader->ReadFloat4();
                this->modelNodeAttrs.SetFloat4(Attr::Rotate, q);
            }
            else if (FourCC('SSCL') == curFourCC)
            {
                // .setscale
                float x = this->binaryReader->ReadFloat();
                float y = this->binaryReader->ReadFloat();
                float z = this->binaryReader->ReadFloat();
                this->modelNodeAttrs.SetFloat4(Attr::Scale, vector(x, y, z));
            }
            else if (FourCC('STXT') == curFourCC)
            {
                // .settexture
                String shaderParam = this->binaryReader->ReadString();
                String texResId = this->binaryReader->ReadString();
                if (AttrId::IsValidName(shaderParam))
                {
                    this->modelNodeAttrs.SetString(shaderParam, texResId);
                }
            }
            else if (FourCC('SINT') == curFourCC)
            {
                // .setint
                String shaderParam = this->binaryReader->ReadString();
                int val = this->binaryReader->ReadInt();
                if (AttrId::IsValidName(shaderParam))
                {
                    this->modelNodeAttrs.SetInt(shaderParam, val);
                }
            }
            else if (FourCC('SFLT') == curFourCC)
            {
                // .setfloat
                String shaderParam = this->binaryReader->ReadString();
                float val = this->binaryReader->ReadFloat();
                if (AttrId::IsValidName(shaderParam))
                {
                    this->modelNodeAttrs.SetFloat(shaderParam, val);
                }
            }
            else if (FourCC('SSHD') == curFourCC)
            {
                // .setshader
                String shaderResId("shd:");
                shaderResId.Append(this->binaryReader->ReadString());
                this->modelNodeAttrs.SetString(Attr::Shader, shaderResId);
            }
            else if (FourCC('SMSH') == curFourCC)
            {
                // .setmesh
                String meshName = this->binaryReader->ReadString();
                this->modelNodeAttrs.SetString(Attr::MeshResourceId, meshName);
            }
            else if (FourCC('SGRI') == curFourCC)
            {
                // .setgroupindex
                int groupIndex = this->binaryReader->ReadInt();
                this->modelNodeAttrs.SetInt(Attr::MeshGroupIndex, groupIndex);
            }
            else
            {
                // skip data block if unknown attribute
                this->stream->Seek(length, Stream::Current);
            }
        }
        else
        {
            stop = true;
        }
    }
    while (!stop && !this->Eof());

    // need to seek back to the beginning of the _new or _sel statement
    this->stream->Seek(-4, Stream::Current);
}

} // namespace Models
